package main

import (
	"archive/zip"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
)

// Thanks https://github.com/mholt/archiver

// ZipCompressor todo
type ZipCompressor struct {
	zw *zip.Writer
	fd *os.File
}

// NewZipCompressor todo
func NewZipCompressor(out string) (*ZipCompressor, error) {
	zc := &ZipCompressor{}
	var err error
	if zc.fd, err = os.Create(out); err != nil {
		return nil, err
	}
	zc.zw = zip.NewWriter(zc.fd)
	return zc, nil
}

// Close todo
func (zc *ZipCompressor) Close() error {
	_ = zc.zw.Close()
	return zc.fd.Close()
}

func (zc *ZipCompressor) writeFile(f File, writer io.Writer) error {
	if f.IsDir() {
		return nil // directories have no contents
	}
	if isSymlink(f) {
		// file body for symlinks is the symlink target
		linkTarget, err := os.Readlink(f.Name())
		if err != nil {
			return fmt.Errorf("%s: readlink: %v", f.Name(), err)
		}
		_, err = writer.Write([]byte(filepath.ToSlash(linkTarget)))
		if err != nil {
			return fmt.Errorf("%s: writing symlink target: %v", f.Name(), err)
		}
		return nil
	}

	if f.ReadCloser == nil {
		return fmt.Errorf("%s: no way to read file contents", f.Name())
	}
	_, err := io.Copy(writer, f)
	if err != nil {
		return fmt.Errorf("%s: copying contents: %v", f.Name(), err)
	}

	return nil
}

// Write write file to zip archive
func (zc *ZipCompressor) Write(f File) error {
	if zc.zw == nil {
		return errors.New("zip archive was not create for writing firts")
	}
	header, err := zip.FileInfoHeader(f)
	if err != nil {
		return ErrorCat(f.Name(), ": getting header: ", err.Error())
	}
	if f.IsDir() {
		header.Name = StrCat(f.CustomName, "/")
		header.Method = zip.Store
	} else {
		header.Name = f.CustomName
		header.Method = zip.Deflate
	}
	writer, err := zc.zw.CreateHeader(header)
	if err != nil {
		return ErrorCat(f.CustomName, ": making header: ", err.Error())
	}
	return zc.writeFile(f, writer)
}

// AddFile todo
func (zc *ZipCompressor) AddFile(src, nameInArchive string) error {
	st, err := os.Stat(src)
	if err != nil {
		return err
	}
	var file io.ReadCloser
	if st.Mode().IsRegular() {
		file, err = os.Open(src)
		if err != nil {
			return fmt.Errorf("%s: opening: %v", src, err)
		}
		defer file.Close()
	}
	err = zc.Write(File{
		FileInfo: FileInfo{
			FileInfo:   st,
			CustomName: filepath.ToSlash(nameInArchive),
		},
		ReadCloser: file,
	})
	if err != nil {
		return fmt.Errorf("%s: writing: %s", src, err)
	}
	return nil
}
